//#include rfm

(function() {

// Ensure that JSON global object is available
if (!window.JSON) {
	window.JSON = {
		/**
		 * Parses a string that's formatted as JSON
		 * @param {String} data The string to parse
		 * @returns {Object} The JavaScript object serialized by the data
		 */
		parse: function(data) {
			return eval('(' + data.replace(/Date\((\d+)\)/g, 'new Date($1)') + ')');
		},
		/**
		 * Serializes an object to JSON
		 * @param {Object} o The object to format as JSON
		 * @returns {String} The JSON string representation of the object
		 */
		stringify: function(o) {
			var items, i, j;
			if (o instanceof Date) {
				return 'Date(' + o.getTime() + ')';
			}
			else if (o instanceof Array) {
				items = [];
				for (i = 0, j = o.length; i < j; i++) {
					items.push(JSON.stringify(o[i]));
				}
				return '[' + items.join(',') + ']';
			}
			else {
				switch (typeof o) {
					case 'object':
						items = [];
						for (i in o) {
							items.push('"' + i + '"' + ':' + JSON.stringify(o[i]));
						}
						return '{' + items.join(',') + '}';
					case 'string':
						return '"' + o.replace('"', '\"') + '"';
					default:
						return o.toString();
				}
			}
		}
	};
}

/**
 * Named channels mapped to XHR objects that are currently active
 */
var _channels = {};

/**
 * Stops any request on a named channel
 * @param {String} channel The name of the channel to stop
 */
function _abort(channel) {
	var otherXhr = _channels[channel];
	if (otherXhr) {
		if (otherXhr.readyState < 4 && otherXhr.readyState > 0) {
			otherXhr.abort();
		}
		otherXhr = null;
		delete _channels[channel];
	}
}

/**
 * Makes an XHR on a named channel
 * @param {String} channel The name of the channel on which to make the request
 * @param {String} type The type of request to make (POST or GET)
 * @param {String} url  The URL to request
 * @param {Object} params (Optional) Values to post in the request
 * @param {Function} onsuccess (Optional) Function to call when the request completed successfully
 * @param {Function} onfailure (Optional) Function to call when the request failed
 * @param {Function} oncomplete (Optional) Function to call when the request has completed (regardless of success or failure)
 * @returns {XMLHttpRequest} The XMLHttpRequest that was generated
 */
function _send(channel, type, url, params, onsuccess, onfailure, oncomplete) {
	var xhr = new XMLHttpRequest(),
		completed = false,
		origAbort = xhr.abort,
		onreadystatechange,
		paramStr = null;
	type = type ? type.toUpperCase() : 'GET';
	if (params) {
		paramStr = rfm.Ajax.parameterize(params);
		if (type == 'GET') {
			url += (url.indexOf('?') > -1 ? '&' : '?') + paramStr;
			paramStr = null;
		}
	}
	try {
		xhr.abort = function() {
			origAbort.call(xhr);
			if (onreadystatechange) {
			    onreadystatechange('abort');
			}
		};
	}
	catch (e) {
	    // Do nothing
    }
    _abort(channel);
	_channels[channel] = xhr;
	xhr.open(type, url, true);
	xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	onreadystatechange = xhr.onreadystatechange = function(reason) {
		if (reason == 'abort') {
			completed = true;
			if (oncomplete) {
				oncomplete.call(xhr);
			}
		}
		if (!xhr || xhr.readyState === 0) {
			if (!completed && oncomplete) {
				oncomplete.call(xhr);
			}
			completed = true;
			if (xhr) {
				xhr.onreadystatechange = null;
			}
		}
		if (!completed && xhr && xhr.readyState === 4) {
			completed = true;
			var successful = false,
				contentType = (xhr.getResponseHeader('Content-Type') || '').toLowerCase(),
				data;
			try {
				successful = !xhr.status && location.protocol === "file:" || (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || xhr.status === 1223 || xhr.status === 0;
			}
			catch (e) {
				if (console && console.log) {
					xhr = xhr || {};
					console.log('An error occurred while sending/receiving data from the URL "' + url + '"');
					console.log(e);
					console.log('XMLHttpRequest status code: ' + xhr.status);
					console.log('XMLHttpRequest status text: ' + xhr.statusText);
				}
			}
			if (successful) {
				if (contentType.indexOf('json') > -1 || url.indexOf('.json') > -1) {
					data = JSON.parse(xhr.responseText);
				}
				else if (contentType.indexOf('xml') > -1) {
					data = xhr.responseXML;
				}
				else {
					data = xhr.responseText;
				}
			}
			else {
				data = xhr.responseText;
			}
			if (successful && onsuccess) {
				onsuccess.call(xhr, data);
			}
			else if (onfailure) {
				if (console && console.log) {
					xhr = xhr || {};
					console.log('An error occurred while sending/receiving data from the URL "' + url + '"');
					console.log('XMLHttpRequest status code: ' + xhr.status);
					console.log('XMLHttpRequest status text: ' + xhr.statusText);
					console.log('Retrieved data: ' + xhr.responseText);
				}
				onfailure.call(xhr, data);
			}
			if (oncomplete) {
				oncomplete.call(xhr, data);
			}
			xhr = xhr.onreadystatechange = null;
		}
		if (completed) {
			delete _channels[channel];
		}
	};
	xhr.send(paramStr);
	return xhr;
}

/**
 * AJAX utility object
 */
rfm.Ajax = {
	/**
	 * Converts a hash into query string parameters
	 * @param {Object<String,Object>} params The dictionary to convert into parameters
	 * @returns {String} The query string version
	 */
	parameterize: function(params) {
		var paramList = [],
			paramName,
			paramValue,
			i,
			j;
		for (paramName in params) {
			paramValue = params[paramName];
			paramName = encodeURIComponent(paramName);
			if (paramValue instanceof Array) {
				for (i = 0, j = paramValue.length; i < j; i++) {
					paramList.push(paramName + '=' + encodeURIComponent(paramValue[i]));
				}
			}
			else {
				paramList.push(paramName + '=' + encodeURIComponent(paramValue));
			}
		}
		return paramList.join('&');
	},
	/**
	 * Stops any request on a named channel
	 * @param {String} channel The name of the channel to stop
	 */
	cancel: function(channel) {
	    _abort(channel);
	},
	/**
	 * Makes a GET request on a named channel
	 * @param {String} channel The name of the channel on which to make the request
	 * @param {String} url  The URL to request
	 * @param {Object} params (Optional) Values to add to the query string for the request
	 * @param {Function} onsuccess (Optional) Function to call when the request completed successfully
	 * @param {Function} onfailure (Optional) Function to call when the request failed
	 * @param {Function} oncomplete (Optional) Function to call when the request has completed (regardless of success or failure)
	 * @returns {XMLHttpRequest} The XMLHttpRequest that was generated
	 */
	get: function(channel, url, params, onsuccess, onfailure, oncomplete) {
		return _send(channel, 'GET', url, params, onsuccess, onfailure, oncomplete);
	},
	/**
	 * Makes a POST request on a named channel
	 * @param {String} channel The name of the channel on which to make the request
	 * @param {String} url  The URL to request
	 * @param {Object} params (Optional) Values to post in the request
	 * @param {Function} onsuccess (Optional) Function to call when the request completed successfully
	 * @param {Function} onfailure (Optional) Function to call when the request failed
	 * @param {Function} oncomplete (Optional) Function to call when the request has completed (regardless of success or failure)
	 * @returns {XMLHttpRequest} The XMLHttpRequest that was generated
	 */
	post: function(channel, url, params, onsuccess, onfailure, oncomplete) {
		return _send(channel, 'POST', url, params, onsuccess, onfailure, oncomplete);
	}
};

})();